// Copyright (c) 2023 Presto Labs Pte. Ltd.
// Author: fengyang

#pragma once

#include <map>
#include <string>

#include "coin/proto/coin_market_enums.pb.h"
#include "coin/proto/coin_order_enums.pb.h"
#include "coin2/base/conversion.h"
#include "coin2/base/time.h"
#include "coin2/exchange/base/feed/update.h"
#include "coin2/exchange/base/feed_util/book_builder.h"
#include "coin2/exchange/base/feed_util/trade.h"
#include "coin2/exchange/base/feed_util/kline.h"
#include "coin2/exchange/base/market/enums.h"
#include "coin2/exchange/util/json_util.h"

namespace coin2::exchange::maicoin::feed_v2 {
namespace impl {
using coin2::exchange::base::market::ExchangeType;
using coin2::exchange::base::market::MarketType;
using coin2::exchange::base::feed::FeedUpdateManager;
using namespace coin::proto;


inline coin2::exchange::feed::TradeSide GetTradeSide(std::string_view side) {
  if (side == "up") {
    return coin2::exchange::feed::TradeSide::TRADE_BUY_SIDE;
  } else if (side == "down") {
    return coin2::exchange::feed::TradeSide::TRADE_SELL_SIDE;
  } else {
    return coin2::exchange::feed::TradeSide::TRADE_UNKNOWN_SIDE;
  }
}

class MaicoinFeedUpdateParser {
 public:
  static bool ParseBook(
      BookBuilder* book,
      const rapidjson::Document& doc) {
    if (doc["a"].Size() == 0 && doc["b"].Size() == 0) {
      return false;
    }

    std::string_view event = doc["e"].GetString();
    int64_t ts = stoll(doc["T"].GetString()) * 1'000'000LL;
    auto tx = book->NewTransaction(ts);
    if (event == "snapshot") {
      tx.Clear();
    }
    for (const auto& pq : doc["a"].GetArray()) {
      tx.UpdateAsk(stod(pq[0].GetString()), stod(pq[1].GetString()));
    }
    for (const auto& pq : doc["b"].GetArray()) {
      tx.UpdateBid(stod(pq[0].GetString()), stod(pq[1].GetString()));
    }

    return true;
  }

  static void ParseTrade(
      const rapidjson::Value& msg,
      Trade* trade) {
    int64_t ts = stoll(msg["T"].GetString()) * 1'000'000LL;
    trade->timestamp = ts;
    trade->fill_qty = stod(msg["v"].GetString());
    trade->price = stod(msg["p"].GetString());
    trade->side = GetTradeSide(msg["tr"].GetString());
  }

  static fastfeed::proto::KlineInterval ParseKlineInterval(std::string_view interval) {
    static std::map<std::string, fastfeed::proto::KlineInterval> interval_map = {
      {"1", fastfeed::proto::KLINE_INTERVAL_1MIN},
      {"3", fastfeed::proto::KLINE_INTERVAL_3MIN},
      {"5", fastfeed::proto::KLINE_INTERVAL_5MIN},
      {"15", fastfeed::proto::KLINE_INTERVAL_15MIN},
      {"30", fastfeed::proto::KLINE_INTERVAL_30MIN},
      {"60", fastfeed::proto::KLINE_INTERVAL_1HOUR},
      {"1440", fastfeed::proto::KLINE_INTERVAL_1DAY}
    };

    auto [iter, succ] = interval_map.emplace(interval, fastfeed::proto::KLINE_INTERVAL_INVALID);
    return iter->second;
  }

  static bool ParseKlineRest(
      const rapidjson::Value& msg,
      Klines* klines,
      fastfeed::proto::KlineInterval interval,
      int64_t timestamp) {
    const auto& data = msg.GetArray();
    int64_t open_timestamp = stoll(data[0].GetString()) * 1'000'000'000LL;
    auto result = klines->AddKline(open_timestamp);
    if (!result) return false;

    Kline* kline;
    bool freezed;
    std::tie(kline, freezed) = result.value();
    kline->open_timestamp = open_timestamp;
    kline->open = stod(data[1].GetString());
    kline->high = stod(data[2].GetString());
    kline->low = stod(data[3].GetString());
    kline->close = stod(data[4].GetString());
    kline->volume = stod(data[5].GetString());
    kline->timestamp = timestamp;
    kline->interval = interval;

    return freezed;
  }
};

}  // namespace impl

using impl::MaicoinFeedUpdateParser;

}  // namespace coin2::exchange::maicoin::feed_v2
